package com.reference.reference2.soft;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import com.reference.reference2.CacheLoader;

public class SoftLRUCache<K, V> {
	
	//用于记录key值的顺序
	private final LinkedList<K> keyList = new LinkedList<>();
	//用于存放数据
	private final Map<K, ReferenceRef> cache = new HashMap<>();
	//cache的最大容量
	private final int capacity;
	//cacheLoader接口提供了一种加载数据的方式
	private final CacheLoader<K, V> cacheLoader;
	// 垃圾Reference的队列，ReferenceQueue里面保存的是已经被删除的对象的引用
	private final ReferenceQueue<V> q = new ReferenceQueue<>();;
	
	public SoftLRUCache(int capacity, CacheLoader<K, V> cacheLoader) {
		super();
		this.capacity = capacity;
		this.cacheLoader = cacheLoader;
	}
	
	// 继承SoftReference，使得每一个实例都具有可识别的标识。
	private class ReferenceRef extends SoftReference<V> {
		private K key;
		public ReferenceRef(K key, V ref, ReferenceQueue<V> q) {
			super(ref, q);
			this.key = key;
		}
	}
	
	/**可将value缓存至cache中，如果当前cache的容量超过了指定容量的大小，则会将最先保存至cache中的数据丢弃掉
	 * @param key
	 * @param value
	 */
	public void put(K key, V value){
		cleanCache();
		if(keyList.size() >= capacity){
			K eldestKey = keyList.removeFirst();
			cache.remove(eldestKey);
		}
		//如果数据已经存在，则从key的队列中删除
		if(keyList.contains(key)){
			keyList.remove(key);
		}
		//将key存放至队尾
		keyList.addLast(key);
		cache.put(key, new ReferenceRef(key, value, q));
	}
	
	/**根据key从cache中获取数据，如果数据存在则先从keyList中删除，然后再插入队尾，否则调用cacheLoadr的load方法进行加载。
	 * @param key
	 * @return
	 */
	public V get(K key){
		V value;
		boolean success = keyList.remove(key);
		if(!success){
			//通过cacheLoader对数据进行加载
			value = cacheLoader.load(key);
			put(key, value);
		}else{
			//如果成功删除，则从cache中返回数据，并且将key再次放入队尾
			value = cache.get(key).get();
			keyList.addLast(key);
		}
		return value;
	}
	
	private void cleanCache() {
		ReferenceRef ref = null;
		while ((ref = (ReferenceRef) q.poll()) != null) {
			System.out.println("ID=" + ref.key + "被移除");
			cache.remove(ref.key);
			keyList.remove(ref.key);
		}
	}


	@Override
	public String toString() {
		return "LRUCache [keyList=" + keyList + "]";
	}
	
}
